using System;
using System.Collections.Generic;
using Firestarter.DB.Schema.BO;
using FireStarterModeller.BO;
using FireStarterModeller.Test;
using NUnit.Framework;
using Rhino.Mocks;

namespace Firestarter.DB_DomainModel.Logic.Test
{
    [TestFixture]
    public class TestDBToDMObjectIdentityImporter : TestBase
    {
        [Test]
        public void Test_Construct()
        {
            //---------------Set up test pack-------------------
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Test Result -----------------------
            Assert.IsNotNull(boIDImporter);
        }

        [Test]
        public void Test_Import_WhenNullDMClass_ShouldThrowError()
        {
            //---------------Set up test pack-------------------
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            try
            {
                IList<string> errorMessages;
                boIDImporter.Import(null, new DBPrimaryKey(), out errorMessages);
                Assert.Fail("expected ArgumentNullException");
            }
                //---------------Test Result -----------------------
            catch (ArgumentNullException ex)
            {
                StringAssert.Contains("Value cannot be null", ex.Message);
                StringAssert.Contains("dmClass", ex.ParamName);
            }
        }

        [Test]
        public void Test_Import_WhenDBPrimaryKeyIsNull_ShouldThrowError()
        {
            //---------------Set up test pack-------------------
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            DMClass dmClass = new DMClass();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            try
            {
                IList<string> errorMessages;
                boIDImporter.Import(dmClass, null, out errorMessages);
                Assert.Fail("expected ArgumentNullException");
            }
                //---------------Test Result -----------------------
            catch (ArgumentNullException ex)
            {
                StringAssert.Contains("Value cannot be null", ex.Message);
                StringAssert.Contains("dbPrimaryKey", ex.ParamName);
            }
        }

        // Story : if already has primary key: should modify old, otherwise should create new
        [Test]
        public void Test_Import_WithNoExistingObjectIdentity_ShouldLinkNewDMObjectIdentityToDMClass()
        {
            //---------------Set up test pack-------------------
            DBTableBuilder builder = new DBTableBuilder(new DBTable());
            DBColumn primaryKeyColumn = builder.AddPrimaryKeyColumn().Column;
            DBPrimaryKey dbPrimaryKey = builder.Table.PrimaryKeyDB;
            DMClass dmClass = new DMClass();
            dmClass.Properties.Add(new DMProperty { PropertyName = primaryKeyColumn.ColumnName, MappedDBColumnID = primaryKeyColumn.ColumnID });
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.IsNull(dmClass.ObjectIdentity);
            Assert.AreEqual(1, dbPrimaryKey.Columns.Count);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity objectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.IsNotNull(objectIdentity);
            Assert.AreSame(objectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(1, objectIdentity.Properties.Count);
            Assert.AreSame(dmClass, objectIdentity.Class);
        }

        //Story: If the mapped ObjectIdentity is different to the one on the DMClass then unrelate the mapped OID and relate to the existing one
        [Test]
        public void Test_Import_WhenMappedDMObjectIdentityBelongsToDifferentDMClass_ShouldUnrelateMappedOneAndMapToTheObjectIdentityForTheCurrentClass()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = CreateDBPrimaryKeyWithMappedDMObjectIdentityWithOneProp();
            DMObjectIdentity oldObjectIdentity = dbPrimaryKey.GetMappedDMObjectIdentity();
            DMClass dmClass = oldObjectIdentity.Class;

            oldObjectIdentity.Class = new DMClass();

            DMObjectIdentity newObjectIdentity = new DMObjectIdentity();
            dmClass.ObjectIdentity = newObjectIdentity;

            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.AreSame(dbPrimaryKey, oldObjectIdentity.MappedDBPrimaryKey);
            Assert.IsNull(newObjectIdentity.MappedDBPrimaryKey);
            Assert.AreNotSame(dmClass, oldObjectIdentity.Class);
            Assert.IsNotNull(oldObjectIdentity.MappedDBPrimaryKey);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity returnedObjectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.AreNotSame(oldObjectIdentity, returnedObjectIdentity);
            Assert.AreSame(dmClass.ObjectIdentity, returnedObjectIdentity);
            Assert.IsNull(oldObjectIdentity.MappedDBPrimaryKey);
            Assert.AreSame(dbPrimaryKey, returnedObjectIdentity.MappedDBPrimaryKey);

            Assert.IsNull(newObjectIdentity.Class);
        }

        //Story: If there is no mapped DMObjectIdentity, then map to the existing one on the DmCLass if it exists
        [Test]
        public void Test_Import_WhenNoMappedDMObjectIdentity_ShouldMapToTheObjectIdentityForTheCurrentClass()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = CreateDBPrimaryKeyWithMappedDMObjectIdentityWithOneProp();

            DMObjectIdentity oldObjectIdentity = dbPrimaryKey.GetMappedDMObjectIdentity();
            DMClass dmClass = oldObjectIdentity.Class;
            var dmProperty = oldObjectIdentity.Properties[0];

            oldObjectIdentity.MappedDBPrimaryKey = null;

            DMObjectIdentity newObjectIdentity = new DMObjectIdentity();
            dmClass.ObjectIdentity = newObjectIdentity;

            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            
            //---------------Assert Precondition----------------
            Assert.IsNull(dbPrimaryKey.GetMappedDMObjectIdentity());
            Assert.AreNotSame(oldObjectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(1, oldObjectIdentity.Properties.Count);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity returnedObjectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.AreNotSame(oldObjectIdentity, returnedObjectIdentity);
            Assert.AreEqual(1, returnedObjectIdentity.Properties.Count);
            Assert.AreSame(dmProperty, returnedObjectIdentity.Properties[0]);
            Assert.AreSame(dbPrimaryKey, returnedObjectIdentity.MappedDBPrimaryKey);
        }

        [Test]
        public void Test_Import_WithNoExistingObjectIdentity_WhenNoColumns_ShouldCreateObjectIdentityWithNoProperties()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = new DBPrimaryKey();
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            DMClass dmClass = new DMClass();
            //---------------Assert Precondition----------------
            Assert.AreEqual(0, dbPrimaryKey.Columns.Count);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity boID = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.IsNotNull(boID);
            Assert.AreEqual(0, boID.Properties.Count);
            Assert.AreEqual(boID.MappedPrimaryKeyID, dbPrimaryKey.PrimaryKeyID);
            Assert.IsNotNull(boID.Generated);
            Assert.IsFalse(boID.Generated.Value);
        }

        [Test]
        public void Test_Import_WithExistingObjectIdentity_WhenNoColumns_ShouldClearExistingDMObjectIdentitiesProperties()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = CreateDBPrimaryKeyWithMappedDMObjectIdentityWithOneProp();
            DMObjectIdentity objectIdentity = dbPrimaryKey.GetMappedDMObjectIdentity();
            DMClass dmClass = objectIdentity.Class;

            dbPrimaryKey.PrimaryKeyColumns[0].MarkForDelete();

            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.AreEqual(0, dbPrimaryKey.Columns.Count);
            Assert.AreEqual(1, objectIdentity.Properties.Count);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity returnedObjectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.IsNotNull(returnedObjectIdentity);
            Assert.AreSame(objectIdentity, returnedObjectIdentity);
            Assert.AreEqual(0, objectIdentity.Properties.Count);
            Assert.AreSame(returnedObjectIdentity.MappedDBPrimaryKey, dbPrimaryKey);
        }

        private DBPrimaryKey CreateDBPrimaryKeyWithMappedDMObjectIdentityWithOneProp()
        {
            DBPrimaryKey dbPrimaryKey = new DBPrimaryKey();
            DBColumn dbColumn = new DBColumn("Some Column");
            dbPrimaryKey.PrimaryKeyColumns.Add(new DBPrimaryKeyColumn { Column = dbColumn });
            DMClass dmClass = CreateBusinessObjectWithPropertyForColumn(dbColumn);
            dmClass.MappedTable = new DBTable();
            dbPrimaryKey.Table = dmClass.MappedTable as DBTable;
            dmClass.ObjectIdentity = new DMObjectIdentity{MappedDBPrimaryKey = dbPrimaryKey};
            dmClass.ObjectIdentity.Properties.Add(dbColumn.GetMappedDMProperty());
            return dbPrimaryKey;
        }

        [Test]
        public void Test_Import_WithNoExistingObjectIdentity_WhenOneColumn_ShouldCreateObjectIdentityWithOneProperty()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = new DBPrimaryKey();
            DBColumn dbColumn = new DBColumn("Some Column");
            DMClass dmClass = CreateBusinessObjectWithPropertyForColumn(dbColumn);
            dbPrimaryKey.AddColumnInfo(dbColumn);
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.IsNull(dmClass.ObjectIdentity);
            Assert.AreEqual(1, dbPrimaryKey.Columns.Count);
            AssertBusinessObjectContainsPropertyForColumn(dmClass, dbColumn);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity objectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.IsNotNull(objectIdentity);
            Assert.AreSame(objectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(1, objectIdentity.Properties.Count);
            DMProperty expectedBoProp = dmClass.Properties[0];
            Assert.AreSame(expectedBoProp, objectIdentity.Properties[0]);
        }

        [Test]
        public void Test_Import_WithExistingObjectIdentity_WhenOneDifferentColumn_ShouldUpdateObjectIdentityProperty()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = CreateDBPrimaryKeyWithMappedDMObjectIdentityWithOneProp();
            DMObjectIdentity objectIdentity = dbPrimaryKey.GetMappedDMObjectIdentity();
            DMClass dmClass = objectIdentity.Class;
            DBTable table = (DBTable)dbPrimaryKey.Table;

            DBColumn newPKColumn = new DBColumn("NewPKColumn");
            table.Columns.Add(newPKColumn);
            dbPrimaryKey.PrimaryKeyColumns[0].Column = newPKColumn;

            DMProperty newPKProperty = new DMProperty{PropertyName = "NewPKColumn"};
            newPKProperty.MappedDBColumn = newPKColumn;
            newPKProperty.Class = dmClass;

            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.AreSame(objectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(1, dbPrimaryKey.Columns.Count);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity returnedObjectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.AreSame(objectIdentity, returnedObjectIdentity);
            Assert.AreSame(objectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(1, objectIdentity.Properties.Count);
            Assert.AreSame(newPKProperty, objectIdentity.Properties[0]);
        }

        [Test]
        public void Test_Import_WithNoExistingObjectIdentity_WhenTwoColumns_ShouldCreateObjectIdentityWithTwoProperties()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = new DBPrimaryKey();
            DBColumn dbColumn = new DBColumn("Some Column");
            DBColumn dbColumnInfo2 = new DBColumn("Some Column1");
            DMClass dmClass = CreateBusinessObjectWithPropertyForTwoColumn(dbColumn, dbColumnInfo2);
            dbPrimaryKey.AddColumnInfo(dbColumn);
            dbPrimaryKey.AddColumnInfo(dbColumnInfo2);
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.IsNull(dmClass.ObjectIdentity);
            Assert.AreEqual(2, dbPrimaryKey.Columns.Count);
            AssertBusinessObjectContainsPropertyForColumn(dmClass, dbColumn);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity objectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
            Assert.IsNotNull(objectIdentity);
            Assert.AreSame(objectIdentity, dmClass.ObjectIdentity);
            Assert.AreEqual(2, objectIdentity.Properties.Count);
            DMProperty expectedBoProp = dmClass.Properties[0];
            Assert.IsTrue(objectIdentity.Properties.Contains(expectedBoProp));
            DMProperty expectedBoProp2 = dmClass.Properties[1];
            Assert.IsTrue(objectIdentity.Properties.Contains(expectedBoProp2));
        }

        [Test]
        public void Test_Import_WhenColumnsNotInBo_ShouldReportError()
        {
            //---------------Set up test pack-------------------
            DBPrimaryKey dbPrimaryKey = new DBPrimaryKey();
            DBColumn dbColumn = new DBColumn("Some Column");
            DBColumn dbColumnInfo2 = new DBColumn("Some Column1");
            DMClass dmClass = CreateBusinessObjectWithPropertyForTwoColumn(dbColumn, dbColumnInfo2);
            DBColumn dbColumnNotInBo = new DBColumn("unmatched column");
            dbPrimaryKey.AddColumnInfo(dbColumnNotInBo);
            DBToDMObjectIdentityImporter boIDImporter = new DBToDMObjectIdentityImporter();
            //---------------Assert Precondition----------------
            Assert.IsNull(dmClass.ObjectIdentity);
            Assert.AreEqual(1, dbPrimaryKey.Columns.Count);
            AssertBusinessObjectContainsPropertyForColumn(dmClass, dbColumn);
            //---------------Execute Test ----------------------
            IList<string> errorMessages;
            DMObjectIdentity objectIdentity = boIDImporter.Import(dmClass, dbPrimaryKey, out errorMessages);
            //---------------Test Result -----------------------
           // Assert.IsNull(objectIdentity);
            Assert.AreEqual(1, errorMessages.Count);
            string message = errorMessages[0];
            string expectedMessage = string.Format("The primary key for business object '{0}' could not be "
                    + "created since the column '{1}' do not exist on the business object"
                    , dmClass.ClassNameBO, dbColumnNotInBo.ColumnName);
            StringAssert.Contains(expectedMessage, message);
        }

        private static DMClass CreateBusinessObjectWithPropertyForColumn(DBColumn dbColumn)
        {
            DMClass dmClass = new DMClass();
            IDBToDMPropertyUpdater dbToDMPropertyUpdater = MockRepository.GenerateStub<IDBToDMPropertyUpdater>();
            DBToDMPropertyImporter dbToDMPropertyImporter = new DBToDMPropertyImporter(dbToDMPropertyUpdater);
            dbToDMPropertyImporter.Import(dmClass, dbColumn);
            return dmClass;
        }
        private static DMClass CreateBusinessObjectWithPropertyForTwoColumn(DBColumn dbColumn, DBColumn dbColumnInfo2)
        {
            DMClass dmClass = new DMClass();
            IDBToDMPropertyUpdater dbToDMPropertyUpdater = MockRepository.GenerateStub<IDBToDMPropertyUpdater>();
            DBToDMPropertyImporter dbToDMPropertyImporter = new DBToDMPropertyImporter(dbToDMPropertyUpdater);
            dbToDMPropertyImporter.Import(dmClass, dbColumn);
            dbToDMPropertyImporter.Import(dmClass, dbColumnInfo2);
            return dmClass;
        }

        private static void AssertBusinessObjectContainsPropertyForColumn(DMClass dmClass, DBColumn info)
        {
            bool found = false;
            foreach (DMProperty propertyInfo in dmClass.Properties)
            {
                if (propertyInfo.MappedDBColumnID != info.ColumnID) continue;
                found = true;
                break;
            }
            string message = string.Format("The business object property for column {0} " + 
                    "was not found for business object {1}", info.ColumnName, dmClass);
            Assert.IsTrue(found,message);
            
        }
    }
}